#include "WindowManager.h"

#include <windowsx.h>

#include "main/Globals.h"

#include "managers/SettingsManager.h"

#include "utils/DxErrorChecker.h"
#include "utils/Timer.h"

using namespace DirectX;

namespace
{
	LRESULT msgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
		switch (msg) {
		// WM_DESTROY is sent when the window is being destroyed.
		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;

		case WM_LBUTTONDOWN:
		case WM_MBUTTONDOWN:
		case WM_RBUTTONDOWN:
			Globals::mMouseProperties->onMouseDown(hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
			return 0;

		case WM_LBUTTONUP:
		case WM_MBUTTONUP:
		case WM_RBUTTONUP:
			Globals::mMouseProperties->onMouseUp(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
			return 0;

		case WM_MOUSEMOVE:
			Globals::mMouseProperties->onMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
			return 0;
		}

		return DefWindowProc(hwnd, msg, wParam, lParam);
	}
}

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	// Forward hwnd on because we can get messages (e.g., WM_CREATE)
	// before CreateWindow returns, and thus before mhMainWnd is valid.
	return msgProc(hwnd, msg, wParam, lParam);
}

void 
MouseProperties::onMouseDown(const HWND hwnd, const int32_t x, const int32_t y) {
	mLastPosition.x = x;
	mLastPosition.y = y;

	SetCapture(hwnd);
}

void 
MouseProperties::onMouseUp(const int32_t x, const int32_t y) {
	mLastPosition.x = x;
	mLastPosition.y = y;

	ReleaseCapture();
}

void 
MouseProperties::onMouseMove(const WPARAM buttonState, const int32_t x, const int32_t y) {
	if ((buttonState & MK_LBUTTON) != 0) {
		// Make each pixel correspond to a quarter of a degree.
		const float dx = XMConvertToRadians(0.15f * static_cast<float>(x - mLastPosition.x));
		const float dy = XMConvertToRadians(0.15f * static_cast<float>(y - mLastPosition.y));

		Globals::gCamera.pitch(dy);
		Globals::gCamera.rotateAboutYAxis(dx);
	}

	mLastPosition.x = x;
	mLastPosition.y = y;
}

WindowData::WindowData() {
    ZeroMemory(&mHandler, sizeof(HWND));

	const HINSTANCE& appInstance = Globals::gApp;

    // Set window class properties
    WNDCLASS windowClass;
    windowClass.style = CS_HREDRAW | CS_VREDRAW;
	windowClass.lpfnWndProc = MainWndProc;
    windowClass.cbClsExtra = 0;
    windowClass.cbWndExtra = 0;
    windowClass.hInstance = appInstance;
    windowClass.hIcon = LoadIcon(0, IDI_APPLICATION);
    windowClass.hCursor = LoadCursor(0, IDC_ARROW);
    windowClass.hbrBackground = reinterpret_cast<HBRUSH> (GetStockObject(NULL_BRUSH));
    windowClass.lpszMenuName = 0;
    windowClass.lpszClassName = L"D3DWndClassName";

    // Register window class	
#if defined(DEBUG) | defined(_DEBUG)
	const ATOM success = RegisterClass(&windowClass);
	assert(success);
#else
	RegisterClass(&windowClass);
#endif

    // Compute window rectangle dimensions based on requested client area dimensions.
	const uint32_t windowWidth = Globals::gSettingsManager->mWindowWidth;
	const uint32_t windowHeight = Globals::gSettingsManager->mWindowHeight;
	RECT rect = { 0, 0, windowWidth, windowHeight };
	AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, false);
    const uint32_t width = rect.right - rect.left;
    const uint32_t height = rect.bottom - rect.top;

    // Create window
	mHandler = CreateWindow(L"D3DWndClassName", L"DirectX", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, appInstance, 0);

	assert(mHandler);

    ShowWindow(mHandler, SW_SHOW);
    UpdateWindow(mHandler);
}